package oracle.olapi.examples;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import oracle.express.olapi.data.full.ExpressDataProvider;
import oracle.express.olapi.transaction.ExpressTransactionProvider;
import oracle.olapi.data.cursor.CompoundCursor;
import oracle.olapi.data.cursor.Cursor;
import oracle.olapi.data.cursor.ValueCursor;
import oracle.olapi.data.source.CursorManagerSpecification;
import oracle.olapi.data.source.FundamentalMetadataProvider;
import oracle.olapi.data.source.Source;
import oracle.olapi.data.source.SpecifiedCursorManager;
import oracle.olapi.metadata.mdm.MdmHierarchy;
import oracle.olapi.metadata.mdm.MdmLevel;
import oracle.olapi.metadata.mdm.MdmLevelHierarchy;
import oracle.olapi.metadata.mdm.MdmMeasure;
import oracle.olapi.metadata.mdm.MdmMeasureDimension;
import oracle.olapi.metadata.mdm.MdmMetadataProvider;
import oracle.olapi.metadata.mdm.MdmPrimaryDimension;
import oracle.olapi.metadata.mdm.MdmSchema;
import oracle.olapi.transaction.NotCommittableException;
import oracle.olapi.transaction.TransactionProvider;

/**
 * Establishes a connection to Oracle OLAP in an Oracle Database instance.
 * Gets the root schema, and gets the MdmMeasureDimension and
 * MdmPrimaryDimension objects from the schema.
 * Creates a hash map to store the MdmObject objects by name, and provides
 * methods to retrieve those objects.
 * Provides methods for creating a Cursor for a Source and for displaying
 * the values of a Cursor.
 * This class uses the CursorPrintWriter class.
 *
 * <P>
 * @author Oracle Corporation
 */
public class Context10g extends Object {
    /**
     * The CursorPrintWriter for this session.
     */
    private CursorPrintWriter cpw = null;
    
    /**
     * The Logger for this session
     */
    private Logger logOutput = null;
    
    /**
     * The ExpressTransactionProvider for this session.
     */
    private ExpressTransactionProvider tp = null;
    /**
     * The ExpressDataProvider for this session.
     */
    private ExpressDataProvider dp = null;
    /**
     * The Oracle JDBC OracleConnection for this session.
     */
    private oracle.jdbc.OracleConnection conn = null;
    /**
     * The MdmMetadataProvider for this session.
     */
    private MdmMetadataProvider mp = null;
    /**
     * The Properties object for storing the command-line arguments and other
     * parameters to use when creating the connection to the database.
     */
    private Properties props = new Properties();
    /**
     * The HashMap to store the metadata objects and their names.
     */
    private Map m_hashMap = null;
    
    private List m_dimensionList = null;
    private List m_measureList = null;
    
    private String _server;
    private String _user;
    
    /**
     * Creates a new Context10g object and specifies that the messages relating
     * the progress of making the connection to the database and of creating
     * the DataProvider, TransactionProvider, and the MetadataProvider are not
     * displayed.
     */
    public Context10g(Properties properties, Logger logger) {
        props = properties;
        logOutput = logger;
        cpw = new CursorPrintWriter(logger.getPrintWriter());
        
        // Connect to the database
        _connect();
    }
    
    public void close() {
       _disconnect();
    }
    
    /**
     * Makes the connection to the Oracle OLAP server.
     */
    private void _connect() {
        showInfo("Connecting to the database...");
        
        showInfo("Loading the JDBC driver...");
        
        try {
            Class.forName("oracle.jdbc.OracleDriver");
            showInfo("JDBC driver loaded.");
        } catch(Exception e) {
            showError("Cannot not load the JDBC driver. " + e);
        }
        
        showInfo("Setting up connection properties...");
        
        // From the Properties object, get the command-line arguments that
        // specify the URL and the username and password to use in creating
        // the connection to the Oracle database.
        // The URL has the following information:
        //    An Oracle JDBC driver
        //    The server running the Oracle database
        //    The number of the port on which Oracle OLAP is listening
        //    The system identifier (SID) of the Oracle instance
        // When running any of the example programs, specify a URL, a username and
        // a password as command-line arguments in the following format:
        //   -url jdbc:oracle:thin:@serverName:portNumber:sid -user username
        //   -password password
        // In the URL specification, replace  "serverName" with the hostname of
        // the server on which the Orcle database is running.
        // Replace "portNumber" with the number of the TCP/IP listener port for
        // the database (which is 1521 by default).
        // Replace "sid" with the system identifier (SID) of the Oracle instance
        // to which you want to connect.
        // An example url is "jdbc:oracle:thin:@myOracleServer:1521:orcl".
        // The example programs use the Global schema. To run an example program
        // specify the username "global" and the password "global".
        // An example of the command-line arguments is the following:
        //  -url jdbc:oracle:thin:@myOracleServer:1521:orcl -user global
        //  -password global
        
        String url = props.getProperty("url");
        String user = props.getProperty("user");
        String password = props.getProperty("password");
        
        String server;
        if ( url.startsWith("jdbc:oracle:thin:") ) {
          int indexAtSign = url.indexOf("@");
          int indexColonAfterServer = url.indexOf(":", indexAtSign);

          server = url.substring(indexAtSign + 1, indexColonAfterServer);
        }
        else server = url;

        setServer(server);
        setUser(user);
        showInfo("Connecting to " + getServer() + " as " + getUser() + "...");
        
        showInfo("Getting the connection...");
        
        try {
            conn = (oracle.jdbc.OracleConnection)
            java.sql.DriverManager.getConnection(url, user, password);
            showInfo("Connection made.");
        } catch(SQLException e) {
            showError("Connection attempt failed." + e);
        }
        
        _createProviders();
    }
    
    public void _disconnect() {
        if ( null != dp ) {
            showInfo("Closing DataProvider");
            dp.close();
            dp = null;
        }
        _closeConnection();
    }
    
    private void _closeConnection() {
        try {
            if ( null != conn ) {
                showInfo("Closing JDBC connection");
                conn.close();
            }
        }
        catch ( Exception e ) {} // ignore
        finally {
            conn = null;
        }
    }
    
    /**
     * Creates and stores the TransactionProvider, ExpressDataProvider, and the
     * MdmMetadataProvider.
     */
    private void _createProviders() {
        showInfo("Creating a TransactionProvider...");
        tp = new ExpressTransactionProvider();
        
        // Add another property to notify Oracle OLAP to use unique values,
        // which include the hierarchy, the level, and the local value for an
        // element of a dimension.
        props.setProperty("HierarchyValueType", "unique");
        
        showInfo("Creating a DataProvider...");
        dp = new ExpressDataProvider(conn, tp, props);
        // Initialize the DataProvider
        try {
            dp.initialize();
        } catch(SQLException e) {
            showError("Cannot not initialize the DataProvider. " + e);
        }
        
        // Get an MdmMetadataProvider.
        showInfo("Getting the MDM MetadataProvider...");
        try {
            mp = (MdmMetadataProvider) dp.getDefaultMetadataProvider();
        } catch (Exception e) {
            showError("Cannot create the MDM metadata provider." + e);
        }
    }
    
    /**
     * Gets the stored DataProvider.
     */
    public ExpressDataProvider getDataProvider() {
        return dp;
    }
    
    public FundamentalMetadataProvider getFundamentalMetadataProvider() {
        return dp.getFundamentalMetadataProvider();
    }
    
    /**
     * Gets the stored MdmMetadataProvider.
     */
    public MdmMetadataProvider getMetadataProvider() {
        return mp;
    }
    
    /**
     * Gets the stored TransactionProvider.
     */
    public TransactionProvider getTransactionProvider() {
        return tp;
    }
    
    /**
     * From the top-level MdmSchema, gets the subschemas.
     * If the user is GLOBAL, then gets the GLOBAL_CAT subschema and gets
     * a list of the MdmDimension objects and the list of MdmMeasure objects
     * for that subschema.
     * If the user is not GLOBAL, then gets a list of MdmDimension objects and
     * a list of MdmMeasure objects from the root MdmSchema.
     * From the lists, the method creates a hash map to store the names of the
     * dimensions and measures.
     *
     * @param schema The top-level Oracle OLAP MdmSchema.
     */
    private Map loadHashMap() {
        MdmSchema schema = mp.getRootSchema();
        // Create an empty HashMap
        Map map = new HashMap();
        
        boolean isGlobal = false;
        MdmSchema schemaToUse = null;
        
        if (getUser().toUpperCase().equals("GLOBAL")) {
            isGlobal = true;
            
            List subSchemas = schema.getSubSchemas();
            Iterator subSchemasItr = subSchemas.iterator();
            
            while (subSchemasItr.hasNext()) {
                schemaToUse = (MdmSchema) subSchemasItr.next();
                if (schemaToUse.getName().equals("GLOBAL_CAT")) {
                    schema = schemaToUse;
                    break;
                }
            }
            
        }
        
        // Get the dimension list
        m_dimensionList = schema.getDimensions();
        Iterator objIter = m_dimensionList.iterator();
        MdmPrimaryDimension mdmPDim = null;
        
        // For each dimension in the list...
        while(objIter.hasNext()) {
            mdmPDim = (MdmPrimaryDimension) objIter.next();
            // Put its upper case name into the hash map as the key with the
            // object as the value.
            map.put(mdmPDim.getName().toUpperCase(), mdmPDim);
        }
        
        // Get the measure list
        if (isGlobal) {
            //m_measureList = schemaToUse.getMeasures();
            m_measureList = schema.getMeasures();
        } else {
            MdmMeasureDimension mDim = (MdmMeasureDimension)
            //schemaToUse.getMeasureDimension();
            schema.getMeasureDimension();
            m_measureList = mDim.getMeasures();
        }
        
        objIter = m_measureList.iterator();
        
        MdmMeasure mdmMeasure = null;
        // For each measure in the list...
        while(objIter.hasNext()) {
            mdmMeasure = (MdmMeasure) objIter.next();
            // Put its upper case name into the hash map as the key with the
            // object as the value.
            map.put(mdmMeasure.getName().toUpperCase(), mdmMeasure);
        }
        
        return map;
    }
    
    /**
     * Gets the MdmMeasure that has the specified name.
     *
     * @param name The name of the MdmMeasure that you want.
     *
     * @return The MdmMeasure that has the specified name or null.
     */
    public MdmMeasure getMdmMeasureByName(String name) {
        // If the hash map has not been created yet, create it.
        if (m_hashMap == null)
            m_hashMap = loadHashMap();
        
        MdmMeasure result = null;
        
        // If the hash map load succeeded...
        if (m_hashMap != null) {
            String val = name.toUpperCase();
            result = (MdmMeasure) m_hashMap.get(val);
        }
        return result;
    }
    
    /**
     * Gets an array of MdmMeasure objects that has one MdmMeasure for each
     * specified name.
     * If the name is not in the hash map of MdmObject objects, the array
     * element for that name is null.
     *
     * @param names The names of the MdmMeasure objects that you want.
     *
     * @return An array of the MdmMeasure objects that have the specified names.
     */
    public MdmMeasure[] getMdmMeasuresByName(String[] names) {
        // If the hash map has not been created yet, create it.
        if (m_hashMap == null)
            m_hashMap = loadHashMap();
        
        // Create an array of nulls.
        MdmMeasure[] mdmMeasures = new MdmMeasure[names.length];
        // If the hash map load succeeded...
        if (m_hashMap != null) {
            // Loop over the names...
            for (int i=0; i<names.length; i++) {
                // Get the HashMap value for this name.
                String val = names[i].toUpperCase();
                mdmMeasures[i] = (MdmMeasure) m_hashMap.get(val);
            }
        }
        return (mdmMeasures);
    }
    
    /**
     * Gets the MdmPrimaryDimension that has the specified name.
     *
     * @param name The name of the MdmPrimaryDimension that you want.
     *
     * @return The MdmPrimaryDimension that has the specified name or null.
     */
    public MdmPrimaryDimension getMdmPrimaryDimensionByName(String name) {
        // If the hash map has not been created yet, create it.
        if (m_hashMap == null)
            m_hashMap = loadHashMap();
        
        MdmPrimaryDimension result = null;
        
        // If the hash map load succeeded...
        if (m_hashMap != null) {
            // Loop over the names
            String val = name.toUpperCase();
            // Get the MdmPrimaryDimension with that name
            result = (MdmPrimaryDimension) m_hashMap.get(val);
        }
        return result;
    }
    
    public MdmHierarchy getHierarchyByName(MdmPrimaryDimension mdmPDim, String name) {
        List mdmHiers = mdmPDim.getHierarchies();
        Iterator mdmHierItr = mdmHiers.iterator();
        while(mdmHierItr.hasNext()) {
            MdmHierarchy mdmHier = (MdmHierarchy) mdmHierItr.next();
            if (mdmHier.getName().equals(name))
                return mdmHier;
        }
        return null;
    }
    
    /**
     * Gets the MdmLevel that has the specified name.
     *
     * @param mdmLvlHier The MdmLevelHierarchy that contains the level that
     *                   you want.
     *
     * @param levelName A String that specifies the name of the level that
     *                  you want.
     *
     * @return The MdmLevel that has the specified name.
     */
    public MdmLevel getLevelByName(MdmLevelHierarchy mdmLvlHier, String levelName) {
        List mdmLevels = mdmLvlHier.getLevels();
        Iterator mdmLevelsItr = mdmLevels.iterator();
        while (mdmLevelsItr.hasNext()) {
            MdmLevel mdmLevel = (MdmLevel) mdmLevelsItr.next();
            if ((mdmLevel.getName()).equals(levelName))
                return mdmLevel;
        }
        return null;
    }
    
    /**
     * Displays a line of text.
     *
     * @param line A String that contains the text that you want to display.
     */
    public void println(String line) {
        logOutput.println(line);
    }
    
    public void showInfo(String message) {
        logOutput.showInfo(message);
    }
    
    public void showError(String message) {
        logOutput.showError(message);
    }
    
    /**
     * Prepares and commits the current Transaction.
     */
    public void commit() {
        try {
            tp.prepareCurrentTransaction();
        } catch(NotCommittableException e) {
            println("Cannot prepare the current Transaction. " + e);
        }
        tp.commitCurrentTransaction();
    }
    
    
    
    /**
     * Creates a Cursor for the specified Source and displays the values of
     * the Cursor, with unique values for the dimension elements.
     */
    public void displayResult(Source source) {
        _displayResult(source, false);
    }
    
    /**
     * Creates a Cursor for the specified Source and displays the values of
     * the Cursor, with unique values for the dimension elements.
     */
    public void displayResult(Source source, boolean dis) {
        _displayResult(source, dis);
    }
    
    /**
     * Creates a Cursor for the specified Source and displays the values of
     * the Cursor, with unique values for the dimension elements if displayLocVal
     * is false and with local values only if displayLocVal is true.
     * This method retrieves local values only when the value separation String
     * is the default double colons (::).
     *
     * @param source The Source for which you want to create a Cursor and
     *               display its values.
     *
     * @param displayLocVal A boolean that specifies whether to display
     *                      unique or local dimension element values.
     */
    private void _displayResult(Source source, boolean displayLocVal) {
        CursorManagerSpecification cursorMngrSpec =
                dp.createCursorManagerSpecification(source);
        SpecifiedCursorManager cursorManager =
                dp.createCursorManager(cursorMngrSpec);
        Cursor cursor = cursorManager.createCursor();
        
        cpw.printCursor(cursor, displayLocVal);

        // Close the CursorManager.
        cursorManager.close();
    }
    
    
    /**
     * Displays the values of the specified Cursor.
     * This method displays the unique value of dimension elements.
     *
     * @param cursor The Cursor that has the values you want to display.
     */
    public void displayCursor(Cursor cursor) {
        _displayCursor(cursor, false);
    }
    
    /**
     * Displays the values of the specified Cursor.
     *
     * @param cursor The Cursor that has the values you want to display.
     *
     * @param displayLocVal A boolean that specifies whether to display
     *                      unique or local dimension element values.
     */
    public void displayCursor(Cursor cursor, boolean displayLocVal) {
        _displayCursor(cursor, displayLocVal);
    }
    
    /**
     * Displays the values of the specified Cursor.
     *
     * @param cursor The Cursor that has the values you want to display.
     *
     * @param displayLocVal A boolean that specifies whether to display
     *                      unique or local dimension element values.
     */
    private void _displayCursor(Cursor cursor, boolean displayLocVal) {
        cpw.printCursor(cursor, displayLocVal);
    }
    
    
    /**
     * Creates a Cursor for the specified Source and displays its values;
     * the values of any dimension elements are only the local values.
     *
     * @param source The Source for which you want to create a Cursor and
     *               display its values.
     */
  /*
  public void displayResultWithLocalValues(Source source)
  {
    _displayResult(source, true);
  }
   */
    
    /**
     * Creates a Cursor for the Source produced by a TopBottomTemplate and
     * displays the values of the Cursor.
     *
     * @param source The Source returned by the getSource method of a
     *               DynamicDefinition for the TopBottomTemplate example.
     */
    public void displayTopBottomResult(Source source) {
        CursorManagerSpecification cursorMngrSpec =
                dp.createCursorManagerSpecification(source);
        SpecifiedCursorManager cursorManager =
                dp.createCursorManager(cursorMngrSpec);
        ValueCursor valueCursor = (ValueCursor) cursorManager.createCursor();
        
        cpw.printTopBottomResult(valueCursor);
        
        // Close the CursorManager.
        cursorManager.close();
    }
    
    /**
     * Display the values of a Cursor in a crosstab format
     */
    public void displayCursorAsCrosstab(CompoundCursor cursor) {
        cpw.printAsCrosstab(cursor);
    }
    
    /**
     * Specify the name of the Oracle OLAP server for the connection.
     */
    public void setServer(String server) {
        _server = server;
    }
    
    /**
     * Get the name of the Oracle OLAP server for the connection.
     */
    public String getServer() {
        return _server;
    }
    
    /**
     * Specify the username for the connection.
     */
    public void setUser(String user) {
        _user = user;
    }
    
    /**
     * Get the username for the connection.
     */
    public String getUser() {
        return _user;
    }
}
